-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Batched Mode #15502
Add Batched Mode #15502
Conversation
React has an unfortunate quirk where updates are sometimes synchronous -- where React starts rendering immediately within the call stack of `setState` — and sometimes batched, where updates are flushed at the end of the current event. Any update that originates within the call stack of the React event system is batched. This encompasses most updates, since most updates originate from an event handler like `onClick` or `onChange`. It also includes updates triggered by lifecycle methods or effects. But there are also updates that originate outside React's event system, like timer events, network events, and microtasks (promise resolution handlers). These are not batched, which results in both worse performance (multiple render passes instead of single one) and confusing semantics. Ideally all updates would be batched by default. Unfortunately, it's easy for components to accidentally rely on this behavior, so changing it could break existing apps in subtle ways. One way to move to a batched-by-default model is to opt into Concurrent Mode (still experimental). But Concurrent Mode introduces additional semantic changes that apps may not be ready to adopt. This commit introduces an additional mode called Batched Mode. Batched Mode enables a batched-by-default model that defers all updates to the next React event. Once it begins rendering, React will not yield to the browser until the entire render is finished. Batched Mode is superset of Strict Mode. It fires all the same warnings. It also drops the forked Suspense behavior used by Legacy Mode, in favor of the proper semantics used by Concurrent Mode. I have not added any public APIs that expose the new mode yet. I'll do that in subsequent commits.
Details of bundled changes.Comparing: 3f058de...e4fdd20 react-art
react-native-renderer
react-test-renderer
react-noop-renderer
react-reconciler
Generated by 🚫 dangerJS |
Should have same semantics as Concurrent Mode.
Yasss. I found myself wishing for batching literally earlier today. Hope this gets released soon. Why couple it to strict mode? It doesn't require the strict semantics, does it? It feels to me like strict and batched are orthogonal (but both required for concurrent compatibility).
I am wary of too many flags though… 😬 |
@sophiebits It does require Strict Mode because of Suspense. The quirky, forked semantics we use for Suspense in Legacy Mode today aren't compatible with the newer features we're planning to add, so we want to move everyone over to the "proper" semantics used by Concurrent Mode instead.
Yeah I share this concern. |
The more immediate problem is that it isn't compatible across mode but also the Legacy Mode experience is much worse semantics. E.g. it breaks a lot of subtle patterns like that you can't rely on layout being available in componentDidMount, all refs possibly being null for a bit. This makes it much harder than it needs to be to write other code in a Suspense tree, but also if you write code that is compatible with Batched or Concurrent Mode, it might not be compatible in Legacy Mode (as in it'll crash). So the downgrade scenario is really bad. I like to think of Suspense not existing in Legacy Mode. It's just a Batched (Strict) Mode feature. To me, that's the main motivation for this mode. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're still planning on dropping the support for nested <ConcurrentMode />
right? (Because the semantics are currently wrong and really complicated to even understand at the edges, and you often don't end up needing or being able to use it anyway. The alternative is a nested root and manually forwarded contexts.)
In that case it seems like this flag doesn't need to be on a per-fiber level. That lets us get rid of that field in prod. That implementation would look very different so is this only phase one?
I don't think the implementation would look that different. If/when we make it per root, I would replace |
To-dos after discussing with @sebmarkbage:
|
Wait this isn't quite right. Batched Mode should only ever have three different expiration times. Sync, Batched or Offscreen. All of which are fixed constants. Scheduler.next, high vs normal pri event etc. shouldn't matter. It's all just one batch. (Discrete matters for when we flush early though. Batched is discrete.) The level scheduled with the scheduler is kind of arbitrary. Can be Normal for Batched. Offscreen can be lower I guess. |
That's what I meant, I'm not getting rid of the |
There are three types of roots: Legacy, Batched, and Concurrent.
Changed my mind. I think Scheduler priority for Batched updates should be Immediate (except for hidden subtrees which will be downgraded to Idle). Mostly for implementation reasons: need to be able to infer the priority level given an expiration time ( |
e7835cf
to
4f5545b
Compare
Treat Sync and Batched expiration times separately. Only Sync updates are pushed to our internal queue of synchronous callbacks. Renamed `flushImmediateQueue` to `flushSyncCallbackQueue` for clarity.
I'm going to deal with |
So this is ready for another review. |
I'm a little confused here, how could you do to implement this? I.e. if something out of control of React(timer, network...), how to make it still be batched?(I mean, not more extra api like You said one way is to use Concurrent Mode but from my personal perspective, the words above is tend to represent that there are some ways to make batched default and we don't need to do anything, that's not true because we must use some opt-in ways |
What does 'React event' mean in 'to the next React event'? |
* Add Batched Mode React has an unfortunate quirk where updates are sometimes synchronous -- where React starts rendering immediately within the call stack of `setState` — and sometimes batched, where updates are flushed at the end of the current event. Any update that originates within the call stack of the React event system is batched. This encompasses most updates, since most updates originate from an event handler like `onClick` or `onChange`. It also includes updates triggered by lifecycle methods or effects. But there are also updates that originate outside React's event system, like timer events, network events, and microtasks (promise resolution handlers). These are not batched, which results in both worse performance (multiple render passes instead of single one) and confusing semantics. Ideally all updates would be batched by default. Unfortunately, it's easy for components to accidentally rely on this behavior, so changing it could break existing apps in subtle ways. One way to move to a batched-by-default model is to opt into Concurrent Mode (still experimental). But Concurrent Mode introduces additional semantic changes that apps may not be ready to adopt. This commit introduces an additional mode called Batched Mode. Batched Mode enables a batched-by-default model that defers all updates to the next React event. Once it begins rendering, React will not yield to the browser until the entire render is finished. Batched Mode is superset of Strict Mode. It fires all the same warnings. It also drops the forked Suspense behavior used by Legacy Mode, in favor of the proper semantics used by Concurrent Mode. I have not added any public APIs that expose the new mode yet. I'll do that in subsequent commits. * Suspense in Batched Mode Should have same semantics as Concurrent Mode. * Use RootTag field to configure type of root There are three types of roots: Legacy, Batched, and Concurrent. * flushSync should not flush batched work Treat Sync and Batched expiration times separately. Only Sync updates are pushed to our internal queue of synchronous callbacks. Renamed `flushImmediateQueue` to `flushSyncCallbackQueue` for clarity.
React has an unfortunate quirk where updates are sometimes synchronous — where React starts rendering immediately within the call stack of
setState
— and sometimes batched, where updates are flushed at the end of the current event. Any update that originates within the call stack of the React event system is batched. This encompasses most updates, since most updates originate from an event handler likeonClick
oronChange
. It also includes updates triggered by lifecycle methods or effects. But there are also updates that originate outside React's event system, like timer events, network events, and microtasks (promise resolution handlers). These are not batched, which results in both worse performance (multiple render passes instead of single one) and confusing semantics.Ideally all updates would be batched by default. Unfortunately, it's easy for components to accidentally rely on this behavior, so changing it could break existing apps in subtle ways.
One way to move to a batched-by-default model is to opt into Concurrent Mode (still experimental). But Concurrent Mode introduces additional semantic changes that apps may not be ready to adopt.
This commit introduces an additional mode called Batched Mode. Batched Mode enables a batched-by-default model that defers all updates to the next React event. Once it begins rendering, React will not yield to the browser until the entire render is finished.
Batched Mode is superset of Strict Mode. It fires all the same warnings. It also drops the forked Suspense behavior used by Legacy Mode, in favor of the proper semantics used by Concurrent Mode.
I have not added any public APIs that expose the new mode yet. I'll do that in subsequent commits.